<template>
  <view
    class="lime-painter"
    ref="limepainter"
  >
    <view
      v-if="canvasId && size"
      :style="styles"
    >
      <!-- #ifndef APP-NVUE -->
      <canvas
        class="lime-painter__canvas"
        v-if="use2dCanvas"
        :id="canvasId"
        type="2d"
        :style="size"
      ></canvas>
      <canvas
        class="lime-painter__canvas"
        v-else
        :id="canvasId"
        :canvas-id="canvasId"
        :style="size"
        :width="boardWidth * dpr"
        :height="boardHeight * dpr"
        :hidpi="hidpi"
      ></canvas>

      <!-- #endif -->
      <!-- #ifdef APP-NVUE -->
      <web-view
        :style="size"
        ref="webview"
        src="/uni_modules/lime-painter/hybrid/html/index.html"
        class="lime-painter__canvas"
        @pagefinish="onPageFinish"
        @error="onError"
        @onPostMessage="onMessage"
      ></web-view>
      <!-- #endif -->
    </view>
    <slot />
  </view>
</template>

<script>
import { parent } from '../common/relation';
import props from './props';
import {
  toPx,
  base64ToPath,
  pathToBase64,
  isBase64,
  sleep,
  getImageInfo,
} from './utils';
//  #ifndef APP-NVUE
import { canIUseCanvas2d, isPC } from './utils';
import Painter from './painter';
// import Painter from '@painter'
const nvue = {};
//  #endif
//  #ifdef APP-NVUE
import nvue from './nvue';
//  #endif
export default {
  name: 'lime-painter',
  mixins: [props, parent('painter'), nvue],
  data() {
    return {
      use2dCanvas: false,
      canvasHeight: 150,
      canvasWidth: null,
      parentWidth: 0,
      inited: false,
      progress: 0,
      firstRender: 0,
      done: false,
      tasks: [],
    };
  },
  computed: {
    styles() {
      return (
        `${this.size}${this.customStyle || ''};` +
        (this.hidden && 'position: fixed; left: 1500rpx;')
      );
    },
    canvasId() {
      return `l-painter${(this._ && this._.uid) || this._uid}`;
    },
    size() {
      if (this.boardWidth && this.boardHeight) {
        return `width:${this.boardWidth}px; height: ${this.boardHeight}px;`;
      }
    },
    dpr() {
      return this.pixelRatio || uni.getSystemInfoSync().pixelRatio;
    },
    boardWidth() {
      const { width = 0 } =
        (this.elements && this.elements.css) || this.elements || this;
      const w = toPx(width || this.width);
      return w || Math.max(w, toPx(this.canvasWidth));
    },
    boardHeight() {
      const { height = 0 } =
        (this.elements && this.elements.css) || this.elements || this;
      const h = toPx(height || this.height);
      return h || Math.max(h, toPx(this.canvasHeight));
    },
    hasBoard() {
      return this.board && Object.keys(this.board).length;
    },
    elements() {
      return this.hasBoard ? this.board : JSON.parse(JSON.stringify(this.el));
    },
  },
  created() {
    this.use2dCanvas = this.type === '2d' && canIUseCanvas2d() && !isPC;
  },
  async mounted() {
    await sleep(30);
    await this.getParentWeith();
    this.$nextTick(() => {
      setTimeout(() => {
        this.$watch('elements', this.watchRender, {
          deep: true,
          immediate: true,
        });
      }, 30);
    });
  },
  // #ifdef VUE3
  unmounted() {
    this.done = false;
    this.inited = false;
    this.firstRender = 0;
    this.progress = 0;
    this.painter = null;
    clearTimeout(this.rendertimer);
  },
  // #endif
  // #ifdef VUE2
  destroyed() {
    this.done = false;
    this.inited = false;
    this.firstRender = 0;
    this.progress = 0;
    this.painter = null;
    clearTimeout(this.rendertimer);
  },
  // #endif
  methods: {
    async watchRender(val, old) {
      if (
        !val ||
        !val.views ||
        (!this.firstRender ? !val.views.length : !this.firstRender) ||
        !Object.keys(val).length ||
        JSON.stringify(val) == JSON.stringify(old)
      )
        return;
      this.firstRender = 1;
      this.progress = 0;
      this.done = false;
      clearTimeout(this.rendertimer);
      this.rendertimer = setTimeout(() => {
        this.render(val);
      }, this.beforeDelay);
    },
    async setFilePath(path, param) {
      let filePath = path;
      const { pathType = this.pathType } = param || this;
      if (pathType == 'base64' && !isBase64(path)) {
        filePath = await pathToBase64(path);
      } else if (pathType == 'url' && isBase64(path)) {
        filePath = await base64ToPath(path);
      }
      if (param && param.isEmit) {
        this.$emit('success', filePath);
      }
      return filePath;
    },
    async getSize(args) {
      const { width } = args.css || args;
      const { height } = args.css || args;
      if (!this.size) {
        if (width || height) {
          this.canvasWidth = width || this.canvasWidth;
          this.canvasHeight = height || this.canvasHeight;
          await sleep(30);
        } else {
          await this.getParentWeith();
        }
      }
    },
    canvasToTempFilePathSync(args) {
      // this.stopWatch && this.stopWatch()
      // this.stopWatch = this.$watch('done', (v) => {
      // 	if (v) {
      // 		this.canvasToTempFilePath(args)
      // 		this.stopWatch && this.stopWatch()
      // 	}
      // }, {
      // 	immediate: true
      // })
      this.tasks.push(args);
      if (this.done) {
        this.runTask();
      }
    },
    runTask() {
      while (this.tasks.length) {
        const task = this.tasks.shift();
        this.canvasToTempFilePath(task);
      }
    },
    // #ifndef APP-NVUE
    getParentWeith() {
      return new Promise((resolve) => {
        uni
          .createSelectorQuery()
          .in(this)
          .select(`.lime-painter`)
          .boundingClientRect()
          .exec((res) => {
            const { width, height } = res[0] || {};
            this.parentWidth = Math.ceil(width || 0);
            this.canvasWidth = this.parentWidth || 300;
            this.canvasHeight = height || this.canvasHeight || 150;
            resolve(res[0]);
          });
      });
    },
    async render(args = {}) {
      if (!Object.keys(args).length) {
        return console.error('空对象');
      }
      this.progress = 0;
      this.done = false;
      // #ifdef APP-NVUE
      this.tempFilePath.length = 0;
      // #endif
      await this.getSize(args);
      const ctx = await this.getContext();

      let { use2dCanvas, boardWidth, boardHeight, canvas, afterDelay } = this;
      if (use2dCanvas && !canvas) {
        return Promise.reject(new Error('canvas 没创建'));
      }
      this.boundary = {
        top: 0,
        left: 0,
        width: boardWidth,
        height: boardHeight,
      };
      this.painter = null;
      if (!this.painter) {
        const { width } = args.css || args;
        const { height } = args.css || args;
        if (!width && this.parentWidth) {
          Object.assign(args, { width: this.parentWidth });
        }
        const param = {
          context: ctx,
          canvas,
          width: boardWidth,
          height: boardHeight,
          pixelRatio: this.dpr,
          useCORS: this.useCORS,
          createImage: getImageInfo.bind(this),
          performance: this.performance,
          listen: {
            onProgress: (v) => {
              this.progress = v;
              this.$emit('progress', v);
            },
            onEffectFail: (err) => {
              this.$emit('faill', err);
            },
          },
        };
        this.painter = new Painter(param);
      }
      try {
        // vue3 赋值给data会引起图片无法绘制
        const { width, height } = await this.painter.source(
          JSON.parse(JSON.stringify(args))
        );
        this.boundary.height = this.canvasHeight = height;
        this.boundary.width = this.canvasWidth = width;
        await sleep(this.sleep);
        await this.painter.render();
        await new Promise((resolve) => this.$nextTick(resolve));
        if (!use2dCanvas) {
          await this.canvasDraw();
        }
        if (afterDelay && use2dCanvas) {
          await sleep(afterDelay);
        }
        this.$emit('done');
        this.done = true;
        if (this.isCanvasToTempFilePath) {
          this.canvasToTempFilePath()
            .then((res) => {
              this.$emit('success', res.tempFilePath);
            })
            .catch((err) => {
              this.$emit('fail', new Error(JSON.stringify(err)));
            });
        }
        this.runTask();
        return Promise.resolve({
          ctx,
          draw: this.painter,
          node: this.node,
        });
      } catch (e) {
        //TODO handle the exception
      }
    },
    canvasDraw(flag = false) {
      return new Promise((resolve, reject) =>
        this.ctx.draw(flag, () => setTimeout(() => resolve(), this.afterDelay))
      );
    },
    async getContext() {
      if (!this.canvasWidth) {
        this.$emit('fail', 'painter no size');
        console.error('[lime-painter]: 给画板或父级设置尺寸');
        return Promise.reject();
      }
      if (this.ctx && this.inited) {
        return Promise.resolve(this.ctx);
      }
      const { type, use2dCanvas, dpr, boardWidth, boardHeight } = this;
      const _getContext = () => {
        return new Promise((resolve) => {
          uni
            .createSelectorQuery()
            .in(this)
            .select(`#${this.canvasId}`)
            .boundingClientRect()
            .exec((res) => {
              if (res) {
                const ctx = uni.createCanvasContext(this.canvasId, this);
                if (!this.inited) {
                  this.inited = true;
                  this.use2dCanvas = false;
                  this.canvas = res;
                }

                // 钉钉小程序框架不支持 measureText 方法，用此方法 mock
                if (!ctx.measureText) {
                  function strLen(str) {
                    let len = 0;
                    for (let i = 0; i < str.length; i++) {
                      if (str.charCodeAt(i) > 0 && str.charCodeAt(i) < 128) {
                        len++;
                      } else {
                        len += 2;
                      }
                    }
                    return len;
                  }
                  ctx.measureText = (text) => {
                    let fontSize = (ctx.state && ctx.state.fontSize) || 12;
                    const font = ctx.__font;
                    if (font && fontSize == 12) {
                      fontSize = parseInt(font.split(' ')[3], 10);
                    }
                    fontSize /= 2;
                    return {
                      width: strLen(text) * fontSize,
                    };
                  };
                }

                // #ifdef MP-ALIPAY
                ctx.scale(dpr, dpr);
                // #endif
                this.ctx = ctx;
                resolve(this.ctx);
              } else {
                console.error('[lime-painter] no node');
              }
            });
        });
      };
      if (!use2dCanvas) {
        return _getContext();
      }
      return new Promise((resolve) => {
        uni
          .createSelectorQuery()
          .in(this)
          .select(`#${this.canvasId}`)
          .node()
          .exec((res) => {
            let { node: canvas } = (res && res[0]) || {};
            if (canvas) {
              const ctx = canvas.getContext(type);
              if (!this.inited) {
                this.inited = true;
                this.use2dCanvas = true;
                this.canvas = canvas;
              }
              this.ctx = ctx;
              resolve(this.ctx);
            } else {
              console.error('[lime-painter]: no size');
            }
          });
      });
    },
    canvasToTempFilePath(args = {}) {
      return new Promise(async (resolve, reject) => {
        const { use2dCanvas, canvasId, dpr, fileType, quality } = this;
        const success = async (res) => {
          try {
            const tempFilePath = await this.setFilePath(
              res.tempFilePath || res,
              args
            );
            const result = Object.assign(res, { tempFilePath });
            args.success && args.success(result);
            resolve(result);
          } catch (e) {
            this.$emit('fail', e);
          }
        };

        let { top: y = 0, left: x = 0, width, height } = this.boundary || this;
        // let destWidth = width * dpr;
        // let destHeight = height * dpr;
        // #ifdef MP-ALIPAY
        // width = destWidth;
        // height = destHeight;
        // #endif

        const copyArgs = Object.assign(
          {
            // x,
            // y,
            // width,
            // height,
            // destWidth,
            // destHeight,
            canvasId,
            id: canvasId,
            fileType,
            quality,
          },
          args,
          { success }
        );
        // if(this.isPC || use2dCanvas) {
        // 	copyArgs.canvas = this.canvas
        // }
        if (use2dCanvas) {
          copyArgs.canvas = this.canvas;
          try {
            // #ifndef MP-ALIPAY
            const oFilePath = this.canvas.toDataURL(
              `image/${args.fileType || fileType}`.replace(/pg/, 'peg'),
              args.quality || quality
            );
            if (/data:,/.test(oFilePath)) {
              uni.canvasToTempFilePath(copyArgs, this);
            } else {
              const tempFilePath = await this.setFilePath(oFilePath, args);
              args.success && args.success({ tempFilePath });
              resolve({ tempFilePath });
            }
            // #endif
            // #ifdef MP-ALIPAY
            this.canvas.toTempFilePath(copyArgs);
            // #endif
          } catch (e) {
            args.fail && args.fail(e);
            reject(e);
          }
        } else {
          // #ifdef MP-ALIPAY
          if (this.ctx.toTempFilePath) {
            // 钉钉
            const ctx = uni.createCanvasContext(canvasId);
            ctx.toTempFilePath(copyArgs);
          } else {
            my.canvasToTempFilePath(copyArgs);
          }
          // #endif
          // #ifndef MP-ALIPAY
          uni.canvasToTempFilePath(copyArgs, this);
          // #endif
        }
      });
    },
    // #endif
  },
};
</script>
<style>
.lime-painter,
.lime-painter__canvas {
  /* #ifndef APP-NVUE */
  width: 100%;
  /* #endif */
  /* #ifdef APP-NVUE*/
  flex: 1;
  /* #endif*/
}
</style>
